/*
 * \brief  CORE implementation of the RM session interface
 * \author Julian Stecklina
 * \date   2008-03-19
 *
 * FIXME arg_string and quota missing
 */

/*
 * Copyright (C) 2008-2009 Julian Stecklina
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* Genode */
#include <base/printf.h>
#include <base/lock.h>
#include <util/misc_math.h>

/* Core */
#include <util.h>
#include <cpu_session_component.h>
#include <rm_session_component.h>
#include <dataspace_component.h>

#include <pistachio/kip.h>

using namespace Genode;


/* DEBUGGING */
static bool verbose = false;
static bool verbose2 = false;


/***************************
 ** Region-manager client **
 ***************************/

int Rm_client::pager(Ipc_pager &pager)
{
	using namespace Pistachio;

	Rm_session::Fault_type pf_type = pager.write_pf() ? Rm_session::WRITE_FAULT
	                                                  : Rm_session::READ_FAULT;
	addr_t pf_addr = pager.pf_addr();
	addr_t pf_ip   = pager.pf_ip();

	if (verbose && verbose2)
		PDBG("pf=%p ip=%p %s from %02lx",
		     (void*)pf_addr, (void*)pf_ip,
		     pf_type == Rm_session::WRITE_FAULT ? "WRITE" : "READ",
		     L4_GlobalId(_tid).global.X.thread_no);

	Rm_session_component *curr_rm_session = member_rm_session();
	Dataspace_component  *dsc;

	off_t  offset = 0;
	addr_t addr   = pf_addr;
	bool   lookup;

	/* traverse potentially nested dataspaces until we hit a leaf dataspace */
	while (true) {
		lookup = curr_rm_session->reverse_lookup(addr, &dsc, &offset);
		if (!lookup)
			break;

		/* check if we need to traverse into a nested dataspace */
		Rm_session_component *sub_rm_session = dsc->sub_rm_session();
		if (!sub_rm_session)
			break;

		/* XXX: detect cycles */

		/* set up next iteration */
		addr            = offset;
		curr_rm_session = sub_rm_session;
	}

	if (!lookup) {

		/*
		 * We found no attachment at the page-fault address and therefore have
		 * to reflect the page fault as region-manager fault. The signal
		 * handler is then expected to request the state of the region-manager
		 * session.
		 */

		/* there is no attachment return an error condition */
		PWRN("no RM attachment (pf=%p ip=%p %s from %02lx (%08lx))",
		     (void*)pf_addr, (void*)pf_ip,
		     pf_type == Rm_session::WRITE_FAULT ? "WRITE" : "READ",
		     L4_GlobalId(_tid).global.X.thread_no,
		     L4_GlobalId(_tid).raw);

		/* register fault at responsible region-manager session */
		curr_rm_session->fault(this, addr, pf_type);
		return 1;
	}

	/*
	 * The 'dsc' dataspace is a leaf dataspace that corresponds to a virtual
	 * address range within core. To prepare the answer for the page fault,
	 * we make sure that this range is mapped in core.
	 */
	addr_t virt_addr = trunc_page(dsc->core_local_addr() + offset);

	/* request mapping of dataspace */
	dsc->map_page(offset, pf_type == Rm_session::WRITE_FAULT);

	/* answer page fault with a flex-page mapping */
	pager.set_reply_fpage(Fpage(pf_addr, virt_addr, get_page_size_log2(),
	                      pf_type == Rm_session::WRITE_FAULT));
	return 0;
}


/*************
 ** Faulter **
 *************/

void Rm_faulter::fault(Rm_session_component *faulting_rm_session,
                       Rm_session::State     fault_state)
{
	Lock::Guard lock_guard(_lock);

	_faulting_rm_session = faulting_rm_session;
	_fault_state         = fault_state;
}


void Rm_faulter::dissolve_from_faulting_rm_session()
{
	Lock::Guard lock_guard(_lock);

	if (_faulting_rm_session)
		_faulting_rm_session->discard_faulter(this);

	_faulting_rm_session = 0;
}


void Rm_faulter::continue_after_resolved_fault()
{
	Lock::Guard lock_guard(_lock);

	/* notify pager to wake up faulter */
	Msgbuf<16> snd, rcv;
	Capability pager = _pager_object->cap();
	Ipc_client ipc_client(pager, &snd, &rcv);
	ipc_client << _pager_object << IPC_CALL;

	_faulting_rm_session = 0;
	_fault_state = Rm_session::State();
}


/**************************************
 ** Region-manager-session component **
 **************************************/

void *Rm_session_component::attach(Dataspace_capability ds_cap, size_t size,
                                   off_t offset, bool use_local_addr, addr_t local_addr)
{
	/* serialize access */
	Lock::Guard lock_guard(_lock);

	/* check dataspace validity */
	Dataspace_component *dsc = dynamic_cast<Dataspace_component *>
	                           (_ds_ep->obj_by_cap(ds_cap));
	if (!dsc) throw Invalid_dataspace();

	if (!size) {
		size = dsc->size();

		if (!size) {
			PWRN("size is 0");
			throw Invalid_dataspace();
		}
	}

	/* work with page granularity */
	size = align_addr(size, Pistachio::get_page_size_log2());

	/* allocate region for attachment */
	void *r = 0;
	if (use_local_addr) {
		if (!_map.alloc_addr(size, local_addr))
			throw Region_conflict();

		r = reinterpret_cast<void *>(local_addr);
	} else {
		if (!_map.alloc_aligned(size, &r, Pistachio::get_page_size_log2()))
			throw Region_conflict();
	}

	/* store attachment info in meta data */
	_map.metadata(r, Rm_region((addr_t)r, size, true, dsc, offset, this));
	Rm_region *region = _map.metadata(r);

	/* also update region list */
	Rm_region_ref *p = new(&_ref_slab) Rm_region_ref(region);
	if (!p) throw Region_conflict();

	_regions.insert(p);

	/* inform dataspace about attachment */
	dsc->attached_to(region);

	if (verbose)
		PDBG("attach ds %p (a=%lx,s=%zx) @ [%lx,%lx)",
		     dsc, dsc->phys_addr(), dsc->size(), (addr_t)r, (addr_t)r + size);

	/* check if attach operation resolves any faulting region-manager clients */
	for (Rm_faulter *faulter = _faulters.first(); faulter; ) {

		/* remeber next pointer before possibly removing current list element */
		Rm_faulter *next = faulter->next();

		if (faulter->fault_in_addr_range((addr_t)r, size)) {
			_faulters.remove(faulter);
			faulter->continue_after_resolved_fault();
		}

		faulter = next;
	}

	return r;
}


void Rm_session_component::detach(void *local_addr)
{
	/* serialize access */
	Lock::Guard lock_guard(_lock);

	/* read meta data for address */
	Rm_region *region = _map.metadata(local_addr);

	if (!region) {
		PWRN("no attachment @ %p", local_addr);
		return;
	}

	Dataspace_component *dsc = region->dataspace();
	if (!dsc)
		PWRN("Rm_region of %p may be inconsistent!", this);

	if (verbose)
		PDBG("detach ds %p (a=%lx,s=%zx) @ [%lx,%lx)",
		     dsc, dsc->phys_addr(), dsc->size(),
		     region->base(), region->base() + region->size());

	/* inform dataspace about detachment */
	dsc->detached_from(region);

	/* update region list */
	Rm_region_ref *p = _regions.first();
	for (; p; p = p->next())
		if (p->region() == region) break;
	if (p) {
		_regions.remove(p);
		destroy(&_ref_slab, p);
	}

	/* deallocate region */
	_map.free(local_addr);

	/* request unmap of dataspace */
	dsc->unmap();
}


Thread_capability Rm_session_component::add_client(Thread_capability thread)
{
	/* serialize access */
	Lock::Guard lock_guard(_lock);

	using namespace Pistachio;

	/* lookup thread and setup correct parameters */
	Cpu_thread *cpu_thread = dynamic_cast<Cpu_thread *>
	                         (_thread_ep->obj_by_cap(thread));
	if (!cpu_thread) throw Invalid_thread();

	Platform_thread *p_thread = cpu_thread->platform_thread();

	L4_ThreadId_t tid = p_thread->l4_thread_id();

	Rm_client *cl = new(&_client_slab) Rm_client(this, tid);
	if (!cl) throw Out_of_memory();

	return Thread_capability(_pager_ep->manage(cl));
}


bool Rm_session_component::reverse_lookup(addr_t                addr,
                                          Dataspace_component **out_dsc,
                                          off_t                *out_offset)
{
	/* serialize access */
	Lock::Guard lock_guard(_lock);

	/* lookup region */
	Rm_region *region = _map.metadata((void*)addr);
	if (!region)
		return false;

	/* request dataspace  backing the region */
	Dataspace_component *dsc = region->dataspace();
	if (!dsc)
		return false;

	/* calculate offset within dataspace */
	off_t offset = addr - region->base() + region->offset();
	if ((offset < 0) || ((unsigned)offset > dsc->size() - 1))
		return false;

	*out_dsc    = dsc;
	*out_offset = offset;
	return true;
}


void Rm_session_component::fault(Rm_faulter *faulter, addr_t pf_addr,
                                 Rm_session::Fault_type pf_type)
{

	/* serialize access */
	Lock::Guard lock_guard(_lock);

	/* remeber fault state in faulting thread */
	faulter->fault(this, Rm_session::State(pf_type, pf_addr));

	/* enqueue faulter */
	_faulters.insert(faulter);

	/* issue fault signal */
	_fault_notifier.submit();
}


void Rm_session_component::discard_faulter(Rm_faulter *faulter)
{
	/* serialize access */
	Lock::Guard lock_guard(_lock);

	_faulters.remove(faulter);
}


void Rm_session_component::fault_handler(Signal_receiver_capability handler)
{
	_fault_notifier.receiver(handler);
}


Rm_session::State Rm_session_component::state()
{
	/* serialize access */
	Lock::Guard lock_guard(_lock);

	/* pick one of the currently faulted threads */
	Rm_faulter *faulter = _faulters.first();

	/* return ready state if there are not current faulters */
	if (!faulter)
		return Rm_session::State();

	/* return fault information regarding the first faulter of the list */
	return faulter->fault_state();
}


Rm_session_component::Rm_session_component(Server_entrypoint *ds_ep,
                                           Server_entrypoint *thread_ep,
                                           Allocator         *md_alloc,
                                           Pager_entrypoint  *pager_ep,
                                           addr_t             vm_start,
                                           size_t             vm_size)
:
	_ds_ep(ds_ep), _thread_ep(thread_ep), _md_alloc(md_alloc),
	_client_slab(md_alloc), _ref_slab(md_alloc), _map(md_alloc), _pager_ep(pager_ep),
	_ds(this), _ds_cap(ds_ep->manage(&_ds))
{
	/* configure managed VM area */
	_map.add_range(vm_start, vm_size);
}


Rm_session_component::~Rm_session_component()
{
	_lock.lock();

	/* revoke dataspace representation */
	_ds_ep->dissolve(&_ds);

	/* remove all faulters with pending page faults at this rm session */
	while (Rm_faulter *faulter = _faulters.first()) {
		_lock.unlock();
		faulter->dissolve_from_faulting_rm_session();
		_lock.lock();
	}

	/* remove all clients */
	while (Rm_client *cl = _client_slab.first_object()) {
		_pager_ep->dissolve(cl);
		_lock.unlock();
		cl->dissolve_from_faulting_rm_session();
		_lock.lock();
		destroy(&_client_slab, cl);
	}

	/* detach all regions */
	while (Rm_region_ref *r = _ref_slab.first_object()) {
		_lock.unlock();
		detach((void *)r->region()->base());
		_lock.lock();
	}

	_lock.unlock();
}
